/**
 * Title:		FrogFactory.java
 * Description: This class implements the Frog logic using Java Swing.
 * In this class, the coordinating system is like Java Swing.
 * 
 * @version		1.0 May 2009
 * @author		Hidemitsu Izawa (hi2136@columbia.edu)
 */

package kfrog;

import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import javax.imageio.ImageIO;

public class FrogFactory {
	/**
	 * fields
	 */
	private int FROG_SIZE;
    private int interval = Const.SPEED3; //default speed = 3
    private double STRIDE = 5.0;//default STRIDE value
    private static Pond pond = null;
    private Graphics canvas;
    private double xpos, ypos;
    private int direction;//from 0 to 360
    private boolean penIsDown;
    private Color penColor = Color.blue; //default color = blue
    private int frogID;
    private boolean isDisplayDebugMessage;


   /**
    * Constructor 
    */
    public FrogFactory(Pond pond, int frogSize, int frogID) {
    	/*this.threadName = name;*/
    	FrogFactory.pond = pond;
    	this.FROG_SIZE = frogSize;
        pond.addFrog(this);
        canvas = pond.makeCanvas();
        xpos = ypos = 0.0;
        direction = 0;
        penIsDown = false;
        this.frogID = frogID;
        this.isDisplayDebugMessage = 
        	KFrogMain.debug || KFrogMain.test || KFrogMain.testSemantics;
        /** debug-output */
        if(isDisplayDebugMessage){
        	/** "}" is written by the run() in Frog.java */
            System.out.println(this.frogID + " new Frog {");
        }
    }


    /**
     * method
     */
    /** reset location and direction for reset command */
    public void resetLocationAndDirection(){
    	xpos = pond.getWidth()/2.0;
    	ypos = pond.getHeight()/2.0;
    	direction = 0;
    }
    
    
    /** set frog speed */
    public void setFrogSpeed(int interval){
    	this.interval = interval;
		/** debug-output */
    	int speed;
    	if(this.interval==Const.SPEED1){
    		speed = 1;
    	}
    	else if(this.interval==Const.SPEED2){
    		speed = 2;
    	}
    	else if(this.interval==Const.SPEED3){
    		speed = 3;
    	}
    	else if(this.interval==Const.SPEED4){
    		speed = 4;
    	}
    	else if(this.interval==Const.SPEED5){
    		speed = 5;
    	}
    	else{
    		speed = 99999;//error
    	}
        if(isDisplayDebugMessage){
        	System.out.println(this.frogID + " speed "+speed);
        }
    }
    
    
    /** repaint for partly*/
    private void repaint() {
    	pond.partlyRepaint();
    }

    
    /** drawing frog icon */
    void paint(Graphics g) {
    	Graphics2D g2 = (Graphics2D)g;
    	Image turtle = loadImage();
    	AffineTransform af = new AffineTransform();
    	af.setToRotation((90-direction)*Math.PI/180,xpos,ypos);
    	g2.setTransform(af);
    	g2.drawImage(turtle,
    				(int)xpos-FROG_SIZE,
    				(int)ypos-FROG_SIZE,
    				FROG_SIZE * 2,
    				FROG_SIZE * 2,
    				null);
    }
    
    
    /** load icon-image file for each colored frog */
	private Image loadImage() {
		/** set icon-image filename */
		String fileName = Const.GREEN_FROG_IMG_FILENAME;//default
		if(penColor.equals(Color.red)){
			fileName = Const.RED_FROG_IMG_FILENAME;
		}
		else if(penColor.equals(Color.black)){
			fileName = Const.BLACK_FROG_IMG_FILENAME;
		}
		else if(penColor.equals(Color.yellow)){
			fileName = Const.YELLOW_FROG_IMG_FILENAME;
		}
		else if(penColor.equals(Color.blue)){
			fileName = Const.BLUE_FROG_IMG_FILENAME;
		}
		else if(penColor.equals(Color.white)){
			fileName = Const.WHITE_FROG_IMG_FINENAME;
		}
		else{
			fileName = Const.GREEN_FROG_IMG_FILENAME;
		}
		/** read from file */
		InputStream is = null;
		try {
			is = new FileInputStream(fileName);
			BufferedImage img = ImageIO.read(is);
			return img;
		} catch (Exception e) {
			throw new RuntimeException(e);
		} finally {
			if (is != null) try { is.close(); } catch (IOException e) {}
		}
	}

	
	/** pend down */
    public void penDown() { 
    	penIsDown = true;
    	/** debug-output */
    	if(isDisplayDebugMessage){
        	System.out.println(this.frogID + " startdraw");
    	}
    }

    /** pen up */
    public void penUp() { 
    	penIsDown = false;
    	/** debug-output */
    	if(isDisplayDebugMessage){
        	System.out.println(this.frogID + " stopdraw");
    	}
    }

    
    /** isDown */
    public boolean isDown() { 
    	return penIsDown;
    }

    
    /** setColor */
    public void setColor(Color c) { 
    	penColor = c;
    }
    
    
    /** setColor */
    public void setColor(String color){
    	if(color.equals(Const.RED)){
    		setColor(Color.red);
    	}
    	else if(color.equals(Const.GREEN)){
    		setColor(Color.green);
    	}
    	else if(color.equals(Const.BLUE)){
    		setColor(Color.blue);
    	}
    	else if(color.equals(Const.YELLOW)){
    		setColor(Color.yellow);
    	}
    	else if(color.equals(Const.BLACK)){
    		setColor(Color.black);
    	}
    	else if(color.equals(Const.WHITE)){
    		setColor(Color.white);
    	}
    	else{
    		setColor(Color.blue);
    	}
		//debug-output
    	if(isDisplayDebugMessage){
        	System.out.println(this.frogID + " frogcolor "+color);
    	}
    }

    
    /** setDirection */
    public void setDirection(double d) { 
    	direction = (int)d;
    }

    
    /** setDirection */
    public void setDirection(int d) { 
    	direction = d;
    }

    
    /** turnleft 
     * @param r rotate angle by degree (ex. 90)
     */
    public synchronized void turnleft(int r) {
    	direction = (direction + r) % 360;
    	//debug-output
    	if(isDisplayDebugMessage){
        	System.out.println(this.frogID + " turnleft "+r);
    	}
    }

    
    /** turnleft */
    public void turnleft(double r) { 
    	turnleft((int)r);
    }
    
    
    /** turnright 
     * @param r rotate angle by degree (ex. 90)
     */
    public synchronized void turnright(int r){
    	direction = (direction + 360 - r ) % 360;
    	//debug-output
    	if(isDisplayDebugMessage){
        	System.out.println(this.frogID + " turnright "+r);
    	}
    }
    
    
    /** turnright */
    public void turnright(double r){
    	turnright((int)r);
    }

    
    /** move forward
     * @param distance distance by int
     */
    public void forward(int distance) {
        forward((double)distance);
    }

    
    /** move forward
     * @param distance distance by double
     */
    public void forward(double distance) {
    	double x = xpos;
    	double y = ypos;
    	if (direction == 0)
    		x += distance;
    	else if (direction == 90)
    		y -= distance;
    	else if (direction == 180)
    		x -= distance;
    	else if (direction == 270)
    		y += distance;
    	else {
    		double r = direction * Math.PI * 2.0 / 360.0;//r:angle[radian]
    		x = xpos + distance * Math.cos(r);
    		/** caution! In JPanel, y direction is downward. */
    		y = ypos - distance * Math.sin(r);//caution!
    	}
    	/** debug-output */
    	if(isDisplayDebugMessage){
        	System.out.println(this.frogID + " forward "+(int)distance);
    	}
    	move(x, y,false);
    }
    
    
    /** move center of the pond */
    public void moveCenter(){
    	move(pond.getWidth()/2.0,pond.getHeight()/2.0,false);
    	/** debug-output */
    	if(isDisplayDebugMessage){
        	System.out.println(this.frogID + " center");
    	}
    }
    

    public void move(int x, int y, boolean isMoveCommand) {
    	move((double)x, (double)y, isMoveCommand);
    }


    /** move to absolute coordinate system
     * @param x x coordinate 
     * @param y y coordinate in JPanel (Java coordination system)
     * @param isMoveCommand To handle debug message correctly.
     */
    public void move(double x, double y, boolean isMoveCommand) {
    	if (penIsDown) {
    		canvas.setColor(penColor);
    		double x2 = xpos - x;
    		double y2 = ypos - y;
    		int step;
    		if (interval > 0) {
    			step = (int)(Math.sqrt(x2 * x2 + y2 * y2) / STRIDE);
                if (step < 2)
                    step = 2;
            }
    		else
    			step = 1;
    		
    		for (int i = step - 1; i >= 0; --i) {
    			double xx = x + x2 * i / step;
    			double yy = y + y2 * i / step;
                pond.setClip((int)xpos, (int)ypos, FROG_SIZE + 2);
                pond.setClip((int)xx, (int)yy, FROG_SIZE + 2);
                /** draw line by step */
                canvas.drawLine((int)xpos, (int)ypos, (int)xx, (int)yy);
                xpos = xx;
                ypos = yy;
                repaint();
                if (i > 0 && interval > 0){
                	try {
                		/** for speed control */
                		Thread.sleep(interval);// small-interval => fast
                	}
                	catch (InterruptedException e) {}
                }
    		}
    	}/** end of if pendown */
        /** if penup */
    	else{
            if(interval > 0){
                try {
                    Thread.sleep(interval / 2);
                }
                catch (InterruptedException e) {
                	e.printStackTrace();
                }
            }
            pond.setClip((int)xpos, (int)ypos, FROG_SIZE + 2);//old position
            xpos = x;
            ypos = y;
            pond.setClip((int)x, (int)y, FROG_SIZE + 2);//new position
            repaint();
        }/** end of else (penup) */
    	/** debug-output */
    	if(isMoveCommand){
    		if(isDisplayDebugMessage){
        		System.out.println(this.frogID + " goto " + 
        				(int)x + "," + (int)(pond.getHeight() - y));
    		}
    	}
    }
    
 
    /** draw left curve */
    public void leftcurve(int radius, int angle){
    	leftcurve((double)radius,(double)angle);
    }
    
    
    /** draw left curve */
    public void leftcurve(double radius, double angle){
    	int oldDirection = direction;
    	double phi = angle * Math.PI / 180.0;
    	double theta = oldDirection * Math.PI / 180.0;//theta[radian]
    	if(penIsDown){
    		canvas.setColor(penColor);
    		double drawArcX = xpos - 
    							radius*Math.cos(theta+3.0*Math.PI/2.0) - radius;
    		double drawArcY = ypos + 
    							radius*Math.sin(theta+3.0*Math.PI/2.0) - radius;
        	int width = 2*((int)radius);
        	int height = 2 *((int)radius);
        	
    		int step;
    		if(interval > 0){
    			step = (int)((phi*radius) / STRIDE );
    			if(step<2){
    				step = 2;
    			}
    		}
    		else{
    			step = 1;
    		}
    		double xx = xpos;
    		double yy = ypos;
			int startAngleInt = (int)(270.0+oldDirection);
			int deltaInt = (int)(((STRIDE/radius)*180.0)/(Math.PI));
			if(deltaInt==0){//for cancellation of significant digits
				deltaInt = 1;
			}
    		for(int i=1; startAngleInt < 270+oldDirection+angle ; i++){
                pond.setClip((int)xpos, (int)ypos, FROG_SIZE + 2);
                canvas.drawArc((int)drawArcX,
                			   (int)drawArcY,
                			   width,
                			   height,
                			   startAngleInt,
                			   deltaInt);
    			startAngleInt = startAngleInt + deltaInt;
    			direction = startAngleInt + 90;
    			xx = drawArcX 
    				 	+ radius
    				 	+ radius
    				 	* Math.cos(((double)(startAngleInt)*Math.PI)/180.0);
    			yy = drawArcY
    					+ radius
    					- radius
    					* Math.sin(((double)(startAngleInt)*Math.PI)/180.0);
                pond.setClip((int)xx, (int)yy, FROG_SIZE + 2);
                xpos = xx;
                ypos = yy;
    			repaint();
                if(i > 0 && interval >0){
                	try {
						Thread.sleep(interval);
					} catch (Exception e) {
					}
                }
    		}
    		direction = oldDirection + (int)angle;//new direction
    	}/** end-of-if */
    	/** if-penup */
    	else{
    		if(interval>0){
    			try {
					Thread.sleep(interval/2);
				} catch (Exception e) {
					e.printStackTrace();
				}
    		}
    		pond.setClip((int)xpos, (int)ypos, FROG_SIZE + 2);//old position
    		direction = direction + (int)angle;//new direction
    		xpos = xpos 
    				- radius*Math.cos(theta+3.0*Math.PI/2.0)
    				+ radius*Math.cos(theta+phi+3.0*Math.PI/2.0);//new xpos
    		ypos = ypos
    				+ radius*Math.sin(theta+3.0*Math.PI/2.0)
    				- radius*Math.sin(theta+phi+3.0*Math.PI/2.0);//new ypos
    		pond.setClip((int)xpos, (int)ypos, FROG_SIZE + 2);//new position
    		repaint();
    	}/** end-of-else (penup) */
    	/** debug-output */
    	if(isDisplayDebugMessage){
        	System.out.println(this.frogID 
        						+ " leftcurve " 
        						+ (int)radius
        						+ ","
        						+ (int)angle);
    	}
    }
    
    
    /** draw right curve */
    public void rightcurve(int radius, int angle){
    	rightcurve((double)radius,(double)angle);
    }
    
    
    /** draw right curve */
    public void rightcurve(double radius, double angle){
    	int oldDirection = direction;
    	double phi = angle * Math.PI / 180.0;
    	double theta = oldDirection * Math.PI / 180.0;//theta[radian]
    	if(penIsDown){
    		canvas.setColor(penColor);
    		double drawArcX = xpos 
    							- radius*Math.cos(theta+Math.PI/2.0)
    							- radius;
    		double drawArcY = ypos
    							+ radius*Math.sin(theta+Math.PI/2.0)
    							- radius;
        	int width = 2*((int)radius);
        	int height = 2 *((int)radius);
        	
    		int step;
    		if(interval > 0){
    			step = (int)((phi*radius) / STRIDE );
    			if(step<2){
    				step = 2;
    			}
    		}
    		else{
    			step = 1;
    		}
    		double xx = xpos;
    		double yy = ypos;
			int startAngleInt = (int)(90.0+oldDirection);
			int deltaInt = (int)(((STRIDE/radius)*180.0)/(Math.PI));
			if(deltaInt==0){//for cancellation of significant digits
				deltaInt = 1;
			}
    		for(int i=1; startAngleInt > 90+oldDirection-angle ; i++){
                pond.setClip((int)xpos, (int)ypos, FROG_SIZE + 2);
                canvas.drawArc((int)drawArcX,
                			   (int)drawArcY,
                			   width,
                			   height,
                			   startAngleInt,
                			   deltaInt);
    			startAngleInt = startAngleInt - deltaInt;
    			direction = startAngleInt - 90;
    			xx = drawArcX
    					+ radius
    					+ radius
    					* Math.cos(((double)(startAngleInt)*Math.PI)/180.0);
    			yy = drawArcY
    					+ radius
    					- radius
    					* Math.sin(((double)(startAngleInt)*Math.PI)/180.0);
                pond.setClip((int)xx, (int)yy, FROG_SIZE + 2);
                xpos = xx;
                ypos = yy;
    			repaint();
                if(i > 0 && interval >0){
                	try {
						Thread.sleep(interval);
					} catch (Exception e) {
					}
                }
    		}
    		direction = oldDirection - (int)angle;//new direction
    	}/** end-of-if */
    	/** if-penup */
    	else{
    		if(interval>0){
    			try {
					Thread.sleep(interval/2);
				} catch (Exception e) {
					e.printStackTrace();
				}
    		}
    		pond.setClip((int)xpos, (int)ypos, FROG_SIZE + 2);//old position
    		direction = direction - (int)angle;//new direction
    		xpos = xpos
    				- radius*Math.cos(theta+Math.PI/2.0)
    				+ radius*Math.cos(theta-phi+Math.PI/2.0);//new xpos
    		ypos = ypos
    				+ radius*Math.sin(theta+Math.PI/2.0)
    				- radius*Math.sin(theta-phi+Math.PI/2.0);//new ypos
    		pond.setClip((int)xpos, (int)ypos, FROG_SIZE + 2);//new position
    		repaint();
    	}/** end-of-else (penup) */
    	/** debug-output */
    	if(isDisplayDebugMessage){
        	System.out.println(this.frogID 
        						+ " rightcurve "
        						+ (int)radius
        						+ ","
        						+ (int)angle);
    	}
    }
    
    
    /** sleep 
     * @param duration sleep duration in milliseconds
     * */
    public void sleep(int duration){
    	sleep((double)duration);
    }
    
    
    /** sleep
     * @param duration sleep duration in milliseconds
     * */
    public void sleep(double duration){
    	try {
			Thread.sleep((long)duration);
		} catch (Exception e) {
			e.printStackTrace();
		}
    }
}
